package ciphers;

/*
 * NOTE! This program uses the Google Collection library (google-collect-1.0.jar) for
 * the BiMap, which is a regular map, but has the ability to reverse the key and value
 * (so the value becomes the key). 
 * 
 * Other than that, it's straight Java5 or higher.
 */
import com.google.common.collect.BiMap;
import com.google.common.collect.HashBiMap;

public class BaconCipher
{
	private static final BiMap<String, String> letter2Key = HashBiMap.create();

	static
	{
		letter2Key.put("a", "AAAAA");
		letter2Key.put("b", "AAAAB");
		letter2Key.put("c", "AAABA");
		letter2Key.put("d", "AAABB");
		letter2Key.put("e", "AABAA");
		letter2Key.put("f", "AABAB");
		letter2Key.put("g", "AABBA");
		letter2Key.put("h", "AABBB");
		letter2Key.put("i", "ABAAA");
		letter2Key.put("j", "ABAAA___");
		letter2Key.put("k", "ABAAB");
		letter2Key.put("l", "ABABA");
		letter2Key.put("m", "ABABB");
		letter2Key.put("n", "ABBAA");
		letter2Key.put("o", "ABBAB");
		letter2Key.put("p", "ABBBA");
		letter2Key.put("q", "ABBBB");
		letter2Key.put("r", "BAAAA");
		letter2Key.put("s", "BAAAB");
		letter2Key.put("t", "BAABA");
		letter2Key.put("u", "BAABB");
		letter2Key.put("v", "BAABB___");
		letter2Key.put("w", "BABAA");
		letter2Key.put("x", "BABAB");
		letter2Key.put("y", "BABBA");
		letter2Key.put("z", "BABBB");
	}

	static String encode(String textToHide, String textToShow)
	{
		/* The text to show should be wordy :) */
		assert (textToHide.length() * 5 > textToShow.length());

		int hideLength = textToHide.length();
		String cipheredText = "";
		for (int pos = 0; pos < hideLength; ++pos)
		{
			cipheredText += letter2Key.get(String.valueOf(textToHide.charAt(pos)));
		}

		cipheredText = cipheredText.replace("null", "");
		cipheredText = cipheredText.replace("___", "");

		int cipheredTextLength = cipheredText.length();
		int textToShowLength = textToShow.length();

		/*
		 * We need to know how long textToShow is without any punctuation
		 */
		int textToShowRealLength = 0;
		for (int pos = 0; pos < textToShowLength; ++pos)
		{
			if (Character.isLetter(textToShow.charAt(pos)))
			{
				++textToShowRealLength;
			}
		}

		/*
		 * First we need to find out what the difference is between the ciphered
		 * text and the text we want to show.
		 */
		int lengthDiff = cipheredTextLength - textToShowRealLength;

		if (lengthDiff > 0)
		{
			/*
			 * cipheredText is longer than textToShow, so we have to pad the
			 * textToShow
			 */
			for (int pad = 0; pad < lengthDiff; ++pad)
				textToShow += "X";
		}

		/*
		 * Okay, now our textToShow is the same length as the cipher, without
		 * any punctuation. Now we need to encode textToShow based on the
		 * cipher...
		 */
		String encodedMessage = "";

		int cipherPos = 0;
		int textPos = 0;
		do
		{
			if (Character.isLetter(textToShow.charAt(textPos)) == false)
			{
				encodedMessage += textToShow.charAt(textPos++);
				continue;
			}

			if (cipheredText.charAt(cipherPos++) == 'A')
			{
				encodedMessage += String.valueOf(textToShow.charAt(textPos++)).toUpperCase();
			}
			else
			{
				encodedMessage += String.valueOf(textToShow.charAt(textPos++)).toLowerCase();
			}

		} while (cipherPos < cipheredTextLength);

		return encodedMessage;
	}

	static String decode(String textToDecipher)
	{
		String baconedText = "";
		for (int pos = 0; pos < textToDecipher.length(); ++pos)
		{
			char currentChar = textToDecipher.charAt(pos);
			/* Uses ASCII values */
			if (currentChar > 64 && currentChar < 91)
			{
				baconedText += "A";
			}
			else if (currentChar > 96 && currentChar < 123)
			{
				baconedText += "B";
			}
		}

		/* Okay, let's re-assemble the secret message */
		BiMap<String, String> key2Letter = letter2Key.inverse();

		String decodedMessage = "";
		int baconLength = baconedText.length();
		for (int pos = 0; pos < baconLength; pos += 5)
		{
			String block = baconedText.substring(pos, pos + 5);
			
			/*
			 * We add a space between each letter because we don't know where
			 * the real word breaks are, and it's easier to read with a spacer
			 * than all bunched up...
			 */
			decodedMessage += (key2Letter.get(block) + " ");
		}

		return decodedMessage;
	}

	public static void main(String[] args)
	{
		String encodedText = BaconCipher.encode("the falcon flies at midnight",
		                                        "the mississippi's mighty starts in minnesota at a place that you could walk across with five steps down");
		System.out.println(encodedText);

		String decodedText = BaconCipher.decode(encodedText);
		System.out.println(decodedText);
	}
}
